using System;
using DevExpress.Xpo;
using PostSharp.Laos;

namespace Atlantic.XpoAutomaticProperties
{
   /// <summary>
   /// Implementation of <see cref="OnMethodBoundaryAspect"/> that calls the 
   /// <see cref="IPropertyChangedNotifier.OnChanged"/> event when a property set
   /// accessor completes successfully.
   /// </summary>
   [Serializable]
   internal class PropertyChangedNotifierSubAspect : OnMethodBoundaryAspect
   {
      #region Constructors

      /// <summary>
      /// Initializes a new <see cref="PropertyChangedNotifierSubAspect"/>.
      /// </summary>
      /// <param name="parent">Parent <see cref="XpoAutomaticPropertiesAttribute"/>.</param>
      /// <param name="propertyName">Name of the property.</param>
      public PropertyChangedNotifierSubAspect(XpoAutomaticPropertiesAttribute parent, string propertyName)
      {
         this.propertyName = propertyName;
         AspectPriority = parent.AspectPriority;
      }

      #endregion

      #region Fields

      private object oldValue;
      private readonly string propertyName;

      #endregion

      #region Methods

      /// <summary>
      /// Method executed <b>before</b> the body of methods to which this aspect is applied.
      /// </summary>
      /// <param name="eventArgs">Event arguments specifying which method
      /// is being executed, which are its arguments, and how should the execution continue
      /// after the execution of <see cref="M:PostSharp.Laos.IOnMethodBoundaryAspect.OnEntry(PostSharp.Laos.MethodExecutionEventArgs)"/>.</param>
      /// <remarks>
      /// If the aspect is applied to a constructor, the current method is invoked
      /// after the <b>this</b> pointer has been initialized, that is, after
      /// the base constructor has been called.
      /// </remarks>
      public override void OnEntry(MethodExecutionEventArgs eventArgs)
      {
         // Store the existing value for later comparison.
         base.OnEntry(eventArgs);
         var instance = (XPBaseObject) eventArgs.Instance;
         oldValue = instance.GetMemberValue(propertyName);
      }

      /// <summary>
      /// Method executed <b>after</b> the body of methods to which this aspect is applied,
      /// but only when the method succesfully returns (i.e. when no exception flies out
      /// the method.).
      /// </summary>
      /// <param name="eventArgs">Event arguments specifying which method
      /// is being executed and which are its arguments.</param>
      public override void OnSuccess(MethodExecutionEventArgs eventArgs)
      {
         // Determine that we have changed values and that we can notify of a property change.
         if (!(eventArgs.Instance is IPropertyChangedNotifier)) return;
         var newValue = eventArgs.GetReadOnlyArgumentArray()[0];
         if (ValuesEqual(oldValue, newValue)) return;

         // Notify of the change.
         var instance = (IPropertyChangedNotifier)eventArgs.Instance;
         instance.OnChanged(propertyName, oldValue, newValue);
      }

      /// <summary>
      /// Helper method to determine if two values are equal.
      /// </summary>
      /// <param name="oldPropertyValue">The old property value.</param>
      /// <param name="newPropertyValue">The new property value.</param>
      /// <returns></returns>
      private bool ValuesEqual(object oldPropertyValue, object newPropertyValue)
      {
         if (ReferenceEquals(oldPropertyValue, newPropertyValue)) return true;
         if (ReferenceEquals(null, oldPropertyValue)) return false;

         if (oldPropertyValue.GetType().IsClass)
         {
            return ReferenceEquals(oldPropertyValue, newPropertyValue);
         }
         return oldPropertyValue.Equals(newPropertyValue);
      }

      #endregion
   }
}